#include "./factory.hh"
#include "../circuit/circuit.hh"
#include "../circuit/componet.hh"
#include "../circuit/subckt.hh"
#include "../circuit/mtulinduction.hh"

#include "../util/util.hh"

#include <format>
#include <memory>
// #include <print>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>

namespace ycspice::preprocess {

    auto Factory::create_subckt(std::string_view circuit_name) -> Circuit* {
        auto op_circuit = this->circuit(circuit_name);
        if (op_circuit.has_value()) {
            return op_circuit.value();
        }

        auto find_res = this->_subckt_infos.find(circuit_name.data());
        if (find_res == this->_subckt_infos.end()) {
            throw std::runtime_error{std::format("No .subckt define of '{}'", circuit_name)};
        }
        return this->create_subckt(find_res->second);
    }


    auto Factory::create_subckt(const SubcktInfo& info) -> Circuit* {
        auto subckt = std::make_unique<Subckt>(info.name);
        for (auto& port : info.ports) {
            subckt->add_port(port);
        }

        for (auto& inst : info.insts) {
            auto tokens = string_split_space(inst);
            
            if (tokens[0][0] == 'k') {
                if (tokens.size() != 4) {
                    throw std::runtime_error{std::format("Bad input: '{}'", inst)};
                }
                subckt->add_mutual_inductances(MutualInductance{
                    std::string{tokens[0]}, std::string{tokens[1]}, std::string{tokens[2]}, std::string{tokens[3]}
                });
                continue;
            }

            Circuit* sub_circuit = nullptr;
            switch (tokens[0][0]) {
                case 'r': {
                    sub_circuit = this->create_componet(ComponetType::Resistance, std::string{tokens[3]});                
                    break;
                }
                case 'c': {
                    sub_circuit = this->create_componet(ComponetType::Capacitance, std::string{tokens[3]});
                    break;
                }
                case 'l': {
                    sub_circuit = this->create_componet(ComponetType::Inductance, std::string{tokens[3]});
                    break;
                }
                case 'x': { 
                    // Create a new one
                    sub_circuit = this->create_subckt(tokens.back());
                    break;
                }
                default: {
                    throw std::runtime_error{std::format("Unsupport compoent '{}' in a .SUBCKT", inst)};
                }
            } 

            // tokens[1..-1] all nets 
            auto net_names = std::vector<std::string>{};
            for (std::size_t i = 1; i < tokens.size() - 1; ++i) {
                net_names.emplace_back(tokens[i]);
            }
            subckt->add_instance(sub_circuit, std::string{tokens.front()}, net_names);
        }

        auto ptr = subckt.get();
        this->_circuits.emplace(info.name, std::move(subckt));
        return ptr;
    }

    auto Factory::create_componet(ComponetType type, std::string value) -> Circuit* {
        auto name = Componet::componet_name(type, value);
        auto find_res = this->_circuits.find(name);
        if (find_res != this->_circuits.end()) {
            return find_res->second.get();
        } else {
            auto circuit = std::make_unique<Componet>(type, std::move(value));
            auto ptr = circuit.get();
            this->_circuits.emplace(std::move(name), std::move(circuit));
            return ptr;
        }
    }

    auto Factory::circuit(std::string_view name) const -> std::optional<Circuit*> {
        auto find_res = this->_circuits.find(name.data());
        if (find_res == this->_circuits.end()) {
            return std::nullopt;
        }  else {
            return find_res->second.get();
        }
    }

    auto Factory::emplace_subckt_info(std::string name, SubcktInfo info) -> void {
        this->_subckt_infos.emplace(std::move(name), std::move(info));
    }

}